package com.qk.management.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qk.common.PageResult;
import com.qk.common.constant.BusinessStatusConstants;
import com.qk.common.constant.ClueTrackRecordConstants;
import com.qk.common.enums.BizEnum;
import com.qk.common.enums.ClueEvent;
import com.qk.common.enums.ClueEnum;
import com.qk.common.exception.BizException;
import com.qk.common.util.CurrentUserHolders;
import com.qk.dto.clue.AddClueDTO;
import com.qk.dto.clue.QueryPageClueDTO;
import com.qk.dto.clue.QueryPoolClueDTO;
import com.qk.dto.clue.TrackClueDTO;
import com.qk.entity.Business;
import com.qk.entity.Clue;
import com.qk.entity.ClueTrackRecord;
import com.qk.entity.User;
import com.qk.management.mapper.BusinessMapper;
import com.qk.management.mapper.ClueMapper;
import com.qk.management.mapper.ClueTrackRecordMapper;
import com.qk.management.mapper.UserMapper;
import com.qk.management.service.ClueService;
import com.qk.vo.clue.QueryClueVO;
import com.qk.vo.clue.QueryPageClueVO;
import com.qk.vo.clue.QueryPoolClueVO;
import com.qk.vo.cluetrackrecord.ClueTrackRecordVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.service.StateMachineService;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * @Author:gzc
 * @Date: 2025/10/06 21:51
 * @Description:
 */
@Service
@Slf4j
public class ClueServiceImpl extends ServiceImpl<ClueMapper,Clue> implements ClueService {

    @Autowired
    private ClueTrackRecordMapper clueTrackRecordMapper;

    @Autowired
    private BusinessMapper businessMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    @Qualifier("clueStateMachineFactory")
    private StateMachineFactory<ClueEnum, ClueEvent> stateMachineFactory;

    @Autowired
    @Qualifier("clueStateMachineService")
    private StateMachineService<ClueEnum, ClueEvent> stateMachineService;

    @Override
    public PageResult<QueryPageClueVO> queryListByPage(QueryPageClueDTO clueQueryListByPageDto) {
        Page<QueryPageClueVO> page = this.baseMapper.queryListByPage(new Page<>(clueQueryListByPageDto.getPage(), clueQueryListByPageDto.getPageSize()), clueQueryListByPageDto);
        return PageResult.<QueryPageClueVO>builder().total(page.getTotal()).rows(page.getRecords()).build();
    }

    @Override
    public void addClue(AddClueDTO addClueDTO) {
        if (ObjectUtil.isEmpty(addClueDTO.getPhone())|| ObjectUtil.isEmpty(addClueDTO.getChannel())){
            BizException.throwBizException(BizEnum.PARAM_ERROR);
        }
        Clue clue = BeanUtil.copyProperties(addClueDTO, Clue.class);
        clue.setStatus(ClueEnum.WAIT_ALLOCATION.getCode());
        clue.setCreateTime(LocalDateTime.now());
        clue.setUpdateTime(LocalDateTime.now());
        this.save(clue);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void assignClue(Integer clueId, Integer userId) {
        if (ObjectUtil.isEmpty(clueId) || ObjectUtil.isEmpty(userId)) {
            BizException.throwBizException(BizEnum.PARAM_ERROR);
        }
        StateMachine<ClueEnum, ClueEvent> stateMachine = null;
        try {
            Clue clue = this.getById(clueId);
            if (ObjectUtil.isEmpty(clue)) {
                throw new BizException(BizEnum.CLUE_NOT_EXIST);
            }
            stateMachine = getClueStateMachine(clueId);
            boolean transitionSuccess = stateMachine.sendEvent(ClueEvent.ASSIGN);
            if (!transitionSuccess) {
                throw new Exception(BizEnum.CLUE_ALLOCATION_FAIL.getMsg());
            }
            ClueEnum newState = stateMachine.getState().getId();
            clue.setStatus(newState.getCode());
            clue.setUserId(userId);
            clue.setUpdateTime(LocalDateTime.now());
            this.updateById(clue);
        } catch (DuplicateKeyException e) {
            throw e;
        } catch (Exception e) {
            log.error("分配线索异常：clueId={}", clueId, e);
            BizException.throwBizException(BizEnum.GENERATE_ERROR);
        } finally {
            releaseClueStateMachine(stateMachine);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void trackClue(TrackClueDTO trackClueDTO)  {
        boolean hasNull = BeanUtil.hasNullField(trackClueDTO, "activityId", "name", "gender", "age", "wechat", "qq", "userId", "status","record",  "createTime", "updateTime");
        if (hasNull){
            BizException.throwBizException(BizEnum.PARAM_ERROR);
        }
        StateMachine<ClueEnum, ClueEvent> stateMachine = null; // 定义在try外
        try {
            if (Objects.equals(ClueEnum.getByCode(trackClueDTO.getStatus()), ClueEnum.WAIT_TRACK)) {
                stateMachine = getClueStateMachine(trackClueDTO.getId());
                boolean transitionSuccess = stateMachine.sendEvent(ClueEvent.START_TRACK);
                if (!transitionSuccess) {
                    throw new Exception(BizEnum.CLUE_TRACK_FAIL.getMsg());
                }
                ClueEnum newState = stateMachine.getState().getId();
                Clue clue = BeanUtil.copyProperties(trackClueDTO, Clue.class);
                clue.setStatus(newState.getCode());
                clue.setUpdateTime(LocalDateTime.now());
                this.baseMapper.updateById(clue);
            }
            ClueTrackRecord clueTrackRecord = BeanUtil.copyProperties(trackClueDTO, ClueTrackRecord.class);
            clueTrackRecord.setId(null);
            clueTrackRecord.setClueId(trackClueDTO.getId());
            clueTrackRecord.setUserId(CurrentUserHolders.getCurrentUserId());
            clueTrackRecord.setCreateTime(LocalDateTime.now());
            clueTrackRecord.setType(ClueTrackRecordConstants.NORMAL_CLUE);
            this.clueTrackRecordMapper.insert(clueTrackRecord);
        } catch (DuplicateKeyException e) {
            throw e;
        } catch (Exception e) {
            log.error("跟进线索异常：clueId={}", trackClueDTO.getId(), e);
            BizException.throwBizException(BizEnum.GENERATE_ERROR);
        } finally {
            releaseClueStateMachine(stateMachine);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void convertToFalse(Integer id, Integer falseReason, String record)  {
        if (ObjectUtil.isEmpty(id) || ObjectUtil.isEmpty(falseReason)) {
            BizException.throwBizException(BizEnum.PARAM_ERROR);
        }
        StateMachine<ClueEnum, ClueEvent> stateMachine = null;
        try {
            Clue clue = this.baseMapper.selectById(id);
            if (ObjectUtil.isEmpty(clue)) {
                throw new BizException(BizEnum.CLUE_NOT_EXIST);
            }
            stateMachine = getClueStateMachine(id);
            boolean transitionSuccess = stateMachine.sendEvent(ClueEvent.MARK_FALSE);
            if (!transitionSuccess) {
                throw new Exception(BizEnum.CLUE_FAKE_FAIL.getMsg());
            }
            ClueEnum newState = stateMachine.getState().getId();
            clue.setStatus(newState.getCode());
            clue.setUpdateTime(LocalDateTime.now());
            this.baseMapper.updateById(clue);
            ClueTrackRecord trackRecord = BeanUtil.copyProperties(clue, ClueTrackRecord.class);
            trackRecord.setId(null);
            trackRecord.setClueId(clue.getId());
            trackRecord.setUserId(CurrentUserHolders.getCurrentUserId());
            trackRecord.setRecord(record);
            trackRecord.setType(ClueTrackRecordConstants.FALSE_CLUE);
            trackRecord.setFalseReason(falseReason);
            clueTrackRecordMapper.insert(trackRecord);
        } catch (BizException | DuplicateKeyException e) {
            throw e;
        } catch (Exception e) {
            log.error("线索转伪线索系统异常：clueId={}", id, e);
            BizException.throwBizException(BizEnum.GENERATE_ERROR);
        } finally {
            releaseClueStateMachine(stateMachine);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void convertToBusiness(Integer id)  {
        if (ObjectUtil.isEmpty(id)) {
            BizException.throwBizException(BizEnum.PARAM_ERROR);
        }
        StateMachine<ClueEnum, ClueEvent> stateMachine = null;
        try {
            Clue clue = this.baseMapper.selectById(id);
            if (ObjectUtil.isEmpty(clue)) {
                throw new BizException(BizEnum.CLUE_NOT_EXIST);
            }
            stateMachine = getClueStateMachine(id);
            boolean transitionSuccess = stateMachine.sendEvent(ClueEvent.CONVERT_TO_BIZ);
            if (!transitionSuccess) {
                throw new Exception(BizEnum.CLUE_CONVERT_BUSINESS_FAIL.getMsg());
            }
            ClueEnum newState = stateMachine.getState().getId();
            clue.setStatus(newState.getCode());
            clue.setUpdateTime(LocalDateTime.now());
            this.updateById(clue);
            Business business = BeanUtil.copyProperties(clue, Business.class);
            business.setId(null);
            business.setUserId(null);
            business.setNextTime(null);
            business.setStatus(BusinessStatusConstants.WAIT_ALLOCATION);
            business.setClueId(clue.getId());
            business.setCreateTime(LocalDateTime.now());
            business.setUpdateTime(LocalDateTime.now());
            this.businessMapper.insert(business);
        } catch (DuplicateKeyException e) {
            throw e;
        } catch (Exception e) {
            log.error("线索转商机系统异常：clueId={}", id, e);
            BizException.throwBizException(BizEnum.GENERATE_ERROR);
        } finally {
            releaseClueStateMachine(stateMachine);
        }
    }



    @Transactional(rollbackFor = Exception.class)
    @Override
    public QueryClueVO queryClueById(Integer id) {
        if (ObjectUtil.isEmpty(id)) {
            BizException.throwBizException(BizEnum.PARAM_ERROR);
        }
        Clue clue = this.baseMapper.selectById(id);
        User user = userMapper.selectById(clue.getUserId());
        QueryClueVO queryClueVO = BeanUtil.copyProperties(clue, QueryClueVO.class);
        List<ClueTrackRecord> clueTrackRecords = clueTrackRecordMapper.selectList(Wrappers.lambdaQuery(ClueTrackRecord.class)
                .eq(ClueTrackRecord::getClueId, id));
        List<ClueTrackRecordVO> collect = clueTrackRecords.stream().map(clueTrackRecord -> {
            ClueTrackRecordVO clueTrackRecordVO = BeanUtil.copyProperties(clueTrackRecord, ClueTrackRecordVO.class);
            clueTrackRecordVO.setAssignName(user.getName());
            return clueTrackRecordVO;
        }).toList();
        queryClueVO.setTrackRecords(collect);
        return queryClueVO;
//        return this.baseMapper.queryClueById(id);
    }

    @Override
    public PageResult<QueryPoolClueVO> queryCluePool(QueryPoolClueDTO queryPoolClueDTO) {
        Page<QueryPoolClueVO> cluePoolPage = this.baseMapper.queryCluePool(new Page<>(queryPoolClueDTO.getPage(), queryPoolClueDTO.getPageSize()), queryPoolClueDTO);
        return PageResult.<QueryPoolClueVO>builder().total(cluePoolPage.getTotal()).rows(cluePoolPage.getRecords()).build();
    }


    /**
     * 获取线索对应的状态机实例（每个线索一个实例，避免并发冲突）
     */
    private StateMachine<ClueEnum, ClueEvent> getClueStateMachine(Integer clueId) {
        // 1. 从数据库加载线索当前状态
        Clue clue = this.getById(clueId);
        ClueEnum currentState = ClueEnum.getByCode(clue.getStatus());

        // 2. 自定义状态机上下文：指定初始状态为数据库状态，且ID为线索ID
        String machineId = clueId.toString();
        DefaultStateMachineContext<ClueEnum, ClueEvent> context =
                new DefaultStateMachineContext<>(
                        currentState,  // 初始状态设为数据库中的状态
                        null,          // 触发事件（初始化时无事件）
                        null,          // 触发器
                        null,          // 扩展变量
                        null,          // 消息头
                        machineId      // 状态机ID（与线索ID绑定）
                );

        // 3. 创建状态机实例，并应用自定义上下文（跳过默认初始状态）
        StateMachine<ClueEnum, ClueEvent> stateMachine = stateMachineFactory.getStateMachine(machineId);
        stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> {
            accessor.resetStateMachine(context); // 强制将状态机初始化为数据库状态
        });
        log.info("StateMachine id: " + stateMachine.getId());
        return stateMachine;
    }

    /**
     * 释放状态机实例
     */
    private void releaseClueStateMachine(StateMachine<ClueEnum, ClueEvent> stateMachine) {
        if (stateMachine == null){
            return;
        }
        stateMachineService.releaseStateMachine(stateMachine.getId());
    }
}
